#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_address.h>

#define DTSLED_NAME   "dtsled"
#define DTSLED_COUNT  1

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO5_IO03;
static void __iomem *SW_PAD_GPIO5_IO03;
static void __iomem *GPIO5_DR;
static void __iomem *GPIO5_GDIR;

struct dtsled_dev {
    dev_t devid;
    int major;
    int minor;
    struct cdev cdev;
    struct class *class;
    struct device *device;

    struct device_node *nd;  // 设备节点
};
struct dtsled_dev dtsled;  // led设备

int dtsled_open (struct inode *node, struct file *filep)
{
    filep->private_data = &dtsled;
    return 0;
}
ssize_t dtsled_write (struct file *filep, const char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    struct dtsled *dev = filep->private_data;
    
    unsigned char databuf[1];
    u32 val;

    ret = __copy_from_user(databuf, buf, 1);
    if(ret < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    if(databuf[0] != 0)  //打开 led
    {
        val = readl(GPIO5_DR);
        val &= (0 << 3);
        writel(val, GPIO5_DR);
    }
    else  //关闭 led
    {
        val = readl(GPIO5_DR);
        val |= (1 << 3);
        writel(val, GPIO5_DR);
    }
    
    return 0;
}

int dtsled_release (struct inode *node, struct file *filep)
{
    struct dtsled_dev *dev = (struct newchrled_dev*)filep->private_data;
    filep->private_data = NULL;

    return 0;
}

// 字符设备操作集合
struct file_operations dtsled_ops = {
    .owner = THIS_MODULE,
    .open = dtsled_open,
    .write = dtsled_write,
    .release = dtsled_release,
};


/**
 * 模块入口
 */
static int __init dtsled_init(void)
{
    int ret;
    const char *str;
    u32 regdata[10];
    u8 i=0;
    u32 val;

/* 注册字符设备 */
    // 1、申请设备号
    if(dtsled.major)
    {
        dtsled.devid = MKDEV(dtsled.major, 0);
        ret = register_chrdev_region(dtsled.devid, DTSLED_COUNT, DTSLED_NAME);
    }
    else{
        ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_COUNT, DTSLED_NAME);
    }
    if(ret < 0){
        goto fail_devid;
    }

    // 2、添加字符设备(初始化设备号)
    dtsled.cdev.owner = THIS_MODULE;
    cdev_init(&dtsled.cdev, &dtsled_ops);
    /* 添加 cdev */
    ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_COUNT);
    if(ret < 0){
        goto fail_cdev;
    }
    
    /* 创建类 */
    dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
    if(IS_ERR(dtsled.class)){
        ret = PTR_ERR(dtsled.class);
        goto fail_class;
    }

    /* 创建设备 */
    dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
    if(IS_ERR(dtsled.device))
    {
        ret = PTR_ERR(dtsled.device);
        goto fail_device;
    }

/* 获取设备树的属性内容 */
    dtsled.nd = of_find_node_by_path("/myled");
    if(dtsled.nd ==NULL){
        ret = -EIO;
        goto find_nd;
    }
    /* 获取 compatible 属性 */
     ret = of_property_read_string(dtsled.nd, "compatible", &str);  //获取字符串信息
     if(ret < 0)
     {
        goto fail_readstr;
     }
     else
     {
        printk("compatible = %s \r\n", str);
     }

     /* 获取 status 属性 */
     ret = of_property_read_string(dtsled.nd, "status", &str);  //获取字符串信息
     if(ret < 0)
     {
        goto fail_readstr;
     }
     else
     {
        printk("status = %s \r\n", str);
     }
#if 0
     /* 获取 reg 数组元素 */
    ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
    if(ret < 0)
    {
        goto fail_read32arry;
    }
    else
    {
        printk("reg data:\r\n");
        for(i = 0; i < 10; i++)
        {
            printk("%#X ", regdata[i]);
        }
        printk("\r\n");
    }

    /* LED 灯初始化 */
    // 1、寄存器地址映射

    IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
    SW_MUX_GPIO5_IO03 = ioremap(regdata[2], regdata[3]);
    SW_PAD_GPIO5_IO03 = ioremap(regdata[4], regdata[5]);
    GPIO5_DR = ioremap(regdata[6], regdata[7]);
    GPIO5_GDIR = ioremap(regdata[8], regdata[9]);
#endif

#if 1
    IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
    SW_MUX_GPIO5_IO03 = of_iomap(dtsled.nd, 1);
    SW_PAD_GPIO5_IO03 = of_iomap(dtsled.nd, 2);
    GPIO5_DR = of_iomap(dtsled.nd, 3);
    GPIO5_GDIR = of_iomap(dtsled.nd, 4);
#endif
    /* 2、使能GPIO5时钟 */
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 30);/* 清除以前的设置 */
    val |= (3 << 30);/* 设置新值 */
    writel(val, IMX6U_CCM_CCGR1);

    /* 3、设置GPIO5_IO03的复用功能，将其复用为GPIO5_IO03。*/
    writel(5, SW_MUX_GPIO5_IO03);
    /* 寄存器SW_PAD_GPIO5_IO03设置IO属性 */
    writel(0x10B0, SW_PAD_GPIO5_IO03);

    /* 4、设置GPIO5_IO03为输出功能 */
    val = readl(GPIO5_GDIR);
    val &= ~(1 << 3); /* 清除以前的设置 */
    val |= (1 << 3); /* 设置为输出 */
    writel(val, GPIO5_GDIR);
   
    /* 5、默认打开LED */
    val = readl(GPIO5_DR);
    val &= (0 << 3);
    writel(val, GPIO5_DR);

    return 0;

fail_read32arry:
fail_readstr:
find_nd:
    device_destroy(dtsled.class, dtsled.devid);
fail_device:
    class_destroy(dtsled.class);
fail_class:
    cdev_del(&dtsled.cdev);
fail_cdev:
    unregister_chrdev_region(dtsled.devid, DTSLED_COUNT);
fail_devid:
    return ret;
}

/**
 * 模块出口
 */
static void __exit dtsled_exit(void)
{
    u32 val;
    /* 关闭LED */
    val = readl(GPIO5_DR);
    val |= (1 << 3);
    writel(val, GPIO5_DR);

    // 取消地址映射
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO5_IO03);
    iounmap(SW_PAD_GPIO5_IO03);
    iounmap(GPIO5_DR);
    iounmap(GPIO5_GDIR);

    device_destroy(dtsled.class, dtsled.devid);
    class_destroy(dtsled.class);
    cdev_del(&dtsled.cdev);
    unregister_chrdev_region(dtsled.devid, DTSLED_COUNT);
}

/**
 * 注册模块入口和出口
 */
module_init(dtsled_init);
module_exit(dtsled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("george <g_yubo@163.com>");
